﻿
Imports std
Imports storoomc
Imports wfileis.data

'assume wfindex is at the same time region as wfileis, otherwise the timeline will be broken with wrong time
'assume wfindex will call only one index method at a certain time, so the tp is free for timeline to use
'store the index in memory, then write back at the end of each index round
Public Module timeline

    Private Class indexResult
        Public Shared userIndex As map(Of pair(Of String, Int64), wfileis.data.timeline.userindex) = Nothing
        Public Shared recordIndex As map(Of pair(Of String, Int64), wfileis.data.timeline.recordindex) = Nothing
        Public Shared categoryIndex As map(Of pair(Of String, Int64), wfileis.data.timeline.categoryindex) = Nothing

        Shared Sub New()
            userIndex = New map(Of pair(Of String, Int64), wfileis.data.timeline.userindex)()
            recordIndex = New map(Of pair(Of String, Int64), wfileis.data.timeline.recordindex)()
            categoryIndex = New map(Of pair(Of String, Int64), wfileis.data.timeline.categoryindex)()
        End Sub
    End Class

    Private Delegate Function generatekeyDelegate(ByVal name As String, ByVal timetick As Int64) As String

    Private Sub insertIndexResult(Of T As wfileis.data.timeline.index) _
                                 (ByVal name As String, ByVal timetick As Int64, _
                                  ByVal key As String, ByVal index As map(Of pair(Of String, Int64), T))
        assert(Not index Is Nothing, "index is nothing.")

        index(make_pair(name, wfileis.data.timeline.index.thisdaytick(timetick))).push_back(key)
        index(make_pair(name, wfileis.data.timeline.index.thisweektick(timetick))).push_back(key)
        index(make_pair(name, wfileis.data.timeline.index.thismonthtick(timetick))).push_back(key)
        index(make_pair(name, wfileis.data.timeline.index.thisyeartick(timetick))).push_back(key)
    End Sub

    Private Function infoTimeline(ByVal key As String, ByRef times As vector(Of Int64), _
                                  Optional ByVal clearOriginalData As Boolean = True) As Boolean
        If times Is Nothing Then
            times = New vector(Of Int64)()
        ElseIf clearOriginalData Then
            times.clear()
        End If

        Dim timetick As Int64 = 0
        If convertor.toint64(key, timetick) Then
            times.push_back(timetick)
            Return True
        Else
            raiseError("cannot convert key " + key + " to int64.", errorHandle.errorType.warning)
            Return False
        End If
    End Function

    Private Function recordTimeline(ByVal key As String, ByRef times As vector(Of Int64), _
                                    Optional ByVal clearOriginalData As Boolean = True) As Boolean
        If times Is Nothing Then
            times = New vector(Of Int64)()
        ElseIf clearOriginalData Then
            times.clear()
        End If

        Dim record As record = Nothing
        record = New record()
        If record.read(key, sourcetalk) Then
            Dim it As record.iterator = Nothing
            it = record.begin()
            While it <> record.end()
                If Not infoTimeline(+it, times, clearOriginalData:=False) Then
                    raiseError("cannot get info timeline " + (+it), errorHandle.errorType.warning)
                End If
                it += 1
            End While

            Return True
        Else
            raiseError("cannot read record " + key, errorHandle.errorType.warning)
            Return False
        End If
    End Function

    Private Function categoryTimeline(ByVal key As String, ByRef times As vector(Of Int64), _
                                      Optional ByVal clearOriginalData As Boolean = True) As Boolean
        If times Is Nothing Then
            times = New vector(Of Int64)()
        ElseIf clearOriginalData Then
            times.clear()
        End If

        Dim category As category = Nothing
        category = New category()
        If category.read(key, sourcetalk) Then
            Dim it As category.iterator = Nothing
            it = category.begin()
            While it <> category.end()
                If Not recordTimeline(+it, times, clearOriginalData:=False) Then
                    raiseError("cannot get record timeline " + (+it), errorHandle.errorType.warning)
                End If
                it += 1
            End While

            Return True
        Else
            raiseError("cannot read category " + key, errorHandle.errorType.warning)
            Return False
        End If
    End Function

    Private Sub infoIndex(ByVal key As String)
        Dim info As info = Nothing
        info = New info()
        If info.read(key, sourcetalk) Then
            Dim times As vector(Of Int64) = Nothing
            If infoTimeline(key, times) Then
                If Not times Is Nothing AndAlso times.size() > 0 Then
                    Dim i As Int64
                    For i = 0 To times.size() - 1
                        insertIndexResult(info.id, times(i), key, indexResult.userIndex)
                    Next
                End If
            Else
                raiseError("cannot get timeline for info " + key, errorHandle.errorType.warning)
            End If
        Else
            raiseError("cannot read info key " + key + " from sourcetalk.", errorHandle.errorType.warning)
        End If
    End Sub

    Private Sub recordIndex(ByVal key As String)
        Dim times As vector(Of Int64) = Nothing
        If recordTimeline(key, times) Then
            If Not times Is Nothing AndAlso times.size() > 0 Then
                Dim i As Int64
                For i = 0 To times.size() - 1
                    'acturally, need only record list instead of record index, anyway
                    insertIndexResult(key, times(i), key, indexResult.recordIndex)
                Next

                'let the first time to be the record created time
                sort(times.rawdata(), 0, times.size())
                'add to empty record index, so i can get all records during a certain time
                insertIndexResult(emptyString, times(0), key, indexResult.recordIndex)
            End If
        Else
            raiseError("cannot get timeline for record " + key, errorHandle.errorType.warning)
        End If
    End Sub

    Private Sub categoryIndex(ByVal key As String)
        Dim times As vector(Of Int64) = Nothing
        If categoryTimeline(key, times) Then
            If Not times Is Nothing AndAlso times.size() > 0 Then
                Dim i As Int64
                For i = 0 To times.size() - 1
                    'acturally, need only category list instead of record index, anyway
                    insertIndexResult(key, times(i), key, indexResult.categoryIndex)
                Next
            End If
        Else
            raiseError("cannot get timeline for category " + key, errorHandle.errorType.warning)
        End If
    End Sub

    Private Delegate Sub indexDelegate(ByVal key As String)

    Private Sub indexImpl(ByVal input As Object)
        Dim p As pair(Of String, indexDelegate) = Nothing
        p = convertor.cast(Of pair(Of String, indexDelegate))(input)
        p.second(p.first)
    End Sub

    Private Sub insertJob(ByVal storekey As String, ByVal oper As indexDelegate)
        assert(Not tp Is Nothing, "threadpool is nothing before timeline.insertJob()")
        tp.insertJob(AddressOf indexImpl, make_pair(Of String, indexDelegate)(storekey, oper))
    End Sub

    Private Sub distribute(ByVal key As String)
        Dim keyprefix As String = Nothing
        Dim storekey As String = Nothing
        Dim type As keytype = keytype.unknown
        type = checkkeytype(key, keyprefix, storekey)
        If type = keytype.info Then
            insertJob(storekey, AddressOf infoIndex)
        ElseIf type = keytype.category Then
            insertJob(storekey, AddressOf categoryIndex)
        ElseIf type = keytype.record Then
            insertJob(storekey, AddressOf recordIndex)
        ElseIf type <> keytype.unknown Then
            assert(False, "should cover all possibilities.")
        End If
    End Sub

    Private Sub initialIndexResultImpl(Of index As wfileis.data.timeline.index) _
                                      (ByRef i As map(Of pair(Of String, Int64), index))
        If i Is Nothing Then
            i = New map(Of pair(Of String, Int64), index)
        Else
            i.clear()
        End If
    End Sub

    Private Sub initialIndexResult()
        initialIndexResultImpl(indexResult.categoryIndex)
        initialIndexResultImpl(indexResult.recordIndex)
        initialIndexResultImpl(indexResult.userIndex)
    End Sub

    Private Sub writeImpl(Of T As wfileis.data.timeline.index)(ByVal key As String, ByVal i As T)
        assert(Not i Is Nothing, "i is nothing.")
        If Not i.modify(key, desttalk) Then
            raiseError("cannot modify index to desttalk.", errorHandle.errorType.warning)
        End If
    End Sub

    Private Sub writeImpl(Of T As wfileis.data.timeline.index)(ByVal i As Object)
        Dim p As pair(Of String, T) = Nothing
        p = convertor.cast(Of pair(Of String, T))(i)
        writeImpl(p.first, p.second)
    End Sub

    Private Sub writelistImpl(Of T As wfileis.data.timeline.list)(ByVal key As String, ByVal i As T)
        assert(Not i Is Nothing, "i is nothing.")
        If Not i.modify(key, desttalk) Then
            raiseError("cannot modify list to desttalk.", errorHandle.errorType.warning)
        End If
    End Sub

    Private Sub writelist(Of T As wfileis.data.timeline.list)(ByVal i As Object)
        Dim p As pair(Of String, T) = Nothing
        p = convertor.cast(Of pair(Of String, T))(i)
        writelistImpl(p.first, p.second)
    End Sub

    Private Sub write(Of T As wfileis.data.timeline.index, T2 As wfileis.data.timeline.list) _
                     (ByVal i As map(Of pair(Of String, Int64), T), ByVal lm As map(Of String, T2))
        assert(Not i Is Nothing, "i is nothing.")
        assert(Not lm Is Nothing, "l is nothing.")

        If i.empty() Then
            raiseError("empty input map(of pair(of string,int64),T) got.", errorHandle.errorType.warning)
        Else
            Dim it As map(Of pair(Of String, Int64), T).iterator = Nothing
            it = i.begin()
            While it <> i.end()
                Dim key As String = Nothing
                key = wfileis.data.timeline.index.generatekey((+it).first.first, (+it).first.second)
                lm((+it).first.first).push_back(key)
                tp.insertJob(AddressOf writeImpl(Of T), make_pair(key, (+it).second))
                it += 1
            End While

            Dim jt As map(Of String, T2).iterator = Nothing
            jt = lm.begin()
            While jt <> lm.end()
                tp.insertJob(AddressOf writelist(Of T2), make_pair((+jt).first(), (+jt).second))
                jt += 1
            End While
        End If
    End Sub

    Private Sub write(ByVal obj As Object)
        Dim c As map(Of pair(Of String, Int64), wfileis.data.timeline.categoryindex) = Nothing
        Dim r As map(Of pair(Of String, Int64), wfileis.data.timeline.recordindex) = Nothing
        Dim u As map(Of pair(Of String, Int64), wfileis.data.timeline.userindex) = Nothing
        If convertor.cast(Of map(Of pair(Of String, Int64), wfileis.data.timeline.categoryindex))(obj, c) Then
            write(c, New map(Of String, wfileis.data.timeline.categorylist))
        ElseIf convertor.cast(Of map(Of pair(Of String, Int64), wfileis.data.timeline.recordindex))(obj, r) Then
            write(r, New map(Of String, wfileis.data.timeline.recordlist))
        ElseIf convertor.cast(Of map(Of pair(Of String, Int64), wfileis.data.timeline.userindex))(obj, u) Then
            write(u, New map(Of String, wfileis.data.timeline.userlist))
        Else
            raiseError("got invalid input object " + obj.GetType().FullName(), errorHandle.errorType.warning)
        End If
    End Sub

    Public Sub index()
        assert(Not fulllist Is Nothing, "fulllist is nothing before timeline.index()")
        assert(tp.idle(), "tp is not idle when timeline.index()")

        initialIndexResult()

        Dim it As [set](Of String).iterator = Nothing
        it = fulllist.begin()
        While it <> fulllist.end()
            distribute(+it)
            it += 1
        End While
        While Not tp.idle()
            sleep(16000)
        End While

        tp.insertJob(AddressOf write, indexResult.categoryIndex)
        tp.insertJob(AddressOf write, indexResult.recordIndex)
        tp.insertJob(AddressOf write, indexResult.userIndex)
        While Not tp.idle()
            sleep(16000)
        End While
    End Sub
End Module
